# @Time   : 2022/1/18 14:53
# @Author : Fioman
# @Phone  : 13149920693
# @Tips   : Talk is Cheap,Show me the code! ^_^^_^
"""
图像处理的公共部分,图像处理的工具部分,都放到这个目录,不管是单拼,还是双拼的,都放到这个目录里来.
"""

import math

import cv2 as cv
import imutils
import numpy as np

from tools.config_params import ConfigParams

innerToEdgeThres = 6  # 单位mm
colorBlue = (255, 0, 0)
colorRed = (0, 0, 255)
colorGreen = (0, 255, 0)
colorList = [colorBlue, colorRed, colorGreen]
sensorDistanceThresBig = 40  # 距离偏大的阈值
sensorDistanceThresSmall = 20  # 距离偏小的阈值
dummyWidthMm = 619  # dummy板的宽
dummyHeightMm = 720  # dummy板的高
dummySizeThres = 15  # dummy板的尺寸阈值范围
dumyyBoardValThres = 70  # dummy板的亮度阈值,小于这个亮度才被认为是dummy板

cp = ConfigParams()


def get_board_without_angle(image, boxMaster, extend=0):
    """
    获取无角度的物料区域,是否延长,表示是否延长物料区域的四周,保证可以完整的看到物料区域.extend表示延长多少的意思.
    :param image:
    :param boxMaster:
    :param extend:延长多少的标识,0表示不延长
    :return:
    """
    (centerX, centerY), (w, h), angle = boxMaster
    widthHalf = int(w / 2)
    heightHalf = int(h / 2)
    yStart = max(int(centerY - heightHalf), 0)  # 之所以有零的情况,是因为旋转之后物料框的y起点可能跑到外面去了.
    yEnd = min(int(centerY + heightHalf), image.shape[0])  # yEnd也是一样的,有可能出去了
    xStart = max(int(centerX - widthHalf), 0)
    xEnd = min(int(centerX + widthHalf), image.shape[1])
    M = cv.getRotationMatrix2D((centerX, centerY), angle, 1)
    if extend != 0:
        if yStart - extend > 0:
            yStart = yStart - extend
        if yEnd + extend < image.shape[0]:
            yEnd = yEnd + extend

        if xStart - extend > 0:
            xStart = xStart - extend
        if xStart + extend < image.shape[1]:
            xEnd = xEnd + extend

    totalBoard = cv.warpAffine(image, M, (image.shape[1], image.shape[0]))[yStart:yEnd, xStart:xEnd]
    return totalBoard, [xStart, yStart]


def image_show(name, image, showState=False):
    """
    显示图片,调试的时候显示图像,按照一定的比例进行缩放
    :param name: 显示的图片的窗口的名称
    :param image: 显示的图片
    :param showState: 是否显示的状态
    :return:
    """
    if showState:
        sizeScale = 4.5
        newWidth = int(image.shape[1] / sizeScale)
        newHeight = int(image.shape[0] / sizeScale)
        showImage = cv.resize(image, (newWidth, newHeight), cv.INTER_AREA)
        cv.namedWindow("{}".format(name))
        cv.imshow("{}".format(name), showImage)
        cv.waitKey(0)


def get_width_height_by_image_name_2p(imageName):
    """
    根据文件名获取板子的信息.包括宽和高,以及首板的到四边的距离
    :param imageName:图像名称
    :return:
    """
    if "w_" not in imageName or "h_" not in imageName:
        return None, None, None, None

    widthIndex = imageName.index("w_")
    heightIndex = imageName.index("h_")
    newW = imageName[widthIndex + 2:widthIndex + 6]
    newH = imageName[heightIndex + 2:heightIndex + 6]

    if "u_" not in imageName or "d_" not in imageName:
        upOffsetList = [0, 0, 0, 0]
        downOffsetList = [0, 0, 0, 0]
    else:
        uIndexStart = imageName.index("u_")
        dIndexStart = imageName.index("d_")
        uIndexEnd = imageName.index("--d")
        dIndexEnd = imageName.index(".bmp")

        upOffsetList = imageName[uIndexStart + 2:uIndexEnd].split("_")
        downOffsetList = imageName[dIndexStart + 2:dIndexEnd].split("_")
        upOffsetList = [int(offset) for offset in upOffsetList]
        downOffsetList = [int(offset) for offset in downOffsetList]

    return int(newW), int(newH), upOffsetList, downOffsetList


def get_width_and_height_by_image_name_3p(imageName):
    """
    根据文件名获取图像信息
    :param imageName:文件名,包括首板确认的宽和高,以及首板的尺寸到板边缘的距离
    :return:
    """
    if "w_" not in imageName or "h_" not in imageName:
        return None, None, None, None, None
    imageName = imageName[:-4]
    dataInfoList = imageName.split("--")
    width, height, upOffset, middleOffset, downOffset = None, None, None, None, None
    for data in dataInfoList:
        if "w_" in data:
            width = data.split("_")[1]
            continue
        if "h_" in data:
            height = data.split("_")[1]
            continue
        if "u_" in data:
            upOffset = data.split("_")[1:5]
            continue
        if "m_" in data:
            middleOffset = data.split("_")[1:5]
            continue
        if "d_" in data:
            downOffset = data.split("_")[1:5]
            continue
    upOffset = [int(x) for x in upOffset] if upOffset is not None else [0, 0, 0, 0]
    middleOffset = [int(x) for x in middleOffset] if middleOffset is not None else [0, 0, 0, 0]
    downOffset = [int(x) for x in downOffset] if downOffset is not None else [0, 0, 0, 0]
    return int(width), int(height), upOffset, middleOffset, downOffset


def get_two_line_distance(innerLine, outterLine):
    """
    获取两条直线的距离,按照第二条线的中点做垂线,然后和第一条线的交点来作为两条直线的距离的评判标准
    :param innerLine: 内层板找到的一条线
    :param outterLine: 物料区域的外层的一条线
    :return:
    """
    innerK, innerB = get_line_k_and_b(innerLine[0], innerLine[1])
    outterK, outterB = get_line_k_and_b(outterLine[0], outterLine[1])
    outterMid = get_two_points_middle(outterLine[0], outterLine[1])

    if outterK == 0:
        # 求外层线中点的垂线和内层线的交点
        intersectionX = outterMid[0]
        intersectionY = innerK * intersectionX + innerB
    elif outterK is None:
        # 如果外层线是垂直的情况,它的垂线的斜率是0,并且过outterMid这个点
        verLineK = 0
        verLineB = outterMid[1]
        intersectionX, intersectionY = get_two_lines_intersection(verLineK, verLineB, innerK, innerB)
    else:
        verLineK = -1 / outterK
        verLineB = outterMid[1] - verLineK * outterMid[0]
        intersectionX, intersectionY = get_two_lines_intersection(verLineK, verLineB, innerK, innerB)

    lineDis = get_two_point_distance(outterMid, (intersectionX, intersectionY))
    return round(lineDis, 2)


def get_new_box_by_edge_and_height(box, height, boardPos, boardColor, upDownSlice=None):
    """
    根据边推测新的框,也就是推测中缝的位置,首先是中缝的位置是错误的,才会走这个逻辑
    :param box: 找到的内板的位置信息,从左上角顺时针的四个顶点
    :param height: 内板的高度
    :param boardPos: 板的位置
    :param boardColor: 用来的显示的板的三通道图片
    :return:
    """
    leftTop, rightTop, rightBottom, leftBottom = box
    leftK, leftB = get_line_k_and_b(leftTop, leftBottom)
    rightK, rightB = get_line_k_and_b(rightTop, rightBottom)
    heightOffset = height

    if boardPos == "down":
        if leftK is None or rightK is None:
            newLeftTop = [int(leftTop[0]), int(leftBottom[1] - heightOffset)]
            newRightTop = [int(rightTop[0]), int(rightBottom[1] - heightOffset)]
        else:
            yOffset = abs(heightOffset * np.sin(trans_k_to_rad(leftK)))
            xOffset = abs(heightOffset * np.cos(trans_k_to_rad(leftK)))
            xOffset = xOffset if leftTop[0] - leftBottom[0] > 0 else -xOffset
            newLeftTop = [int(leftBottom[0] + xOffset), int(leftBottom[1] - yOffset)]
            newRightTop = [int(rightBottom[0] + xOffset), int(rightBottom[1] - yOffset)]
        newLeftBottom = leftBottom
        newRightBottom = rightBottom
        if upDownSlice is not None:
            if min(newLeftTop[1], newRightTop[1]) < upDownSlice - 8 * cp.mm2pix:
                extendLineIsOk = False
            else:
                extendLineIsOk = True
        else:
            if min(newLeftTop[1], newRightTop[1]) < 10 * cp.mm2pix:
                extendLineIsOk = False
            else:
                extendLineIsOk = True
    else:
        if leftK is None or rightK is None:
            newLeftBottom = [int(leftBottom[0]), int(leftTop[1] + heightOffset)]
            newRightBottom = [int(rightBottom[0]), int(rightTop[1] + heightOffset)]
        else:
            yOffset = abs(heightOffset * np.sin(trans_k_to_rad(leftK)))
            xOffset = abs(heightOffset * np.cos(trans_k_to_rad(leftK)))
            xOffset = xOffset if leftBottom[0] - leftTop[0] > 0 else -xOffset
            newLeftBottom = [int(leftTop[0] + xOffset), int(leftTop[1] + yOffset)]
            newRightBottom = [int(rightTop[0] + xOffset), int(rightTop[1] + yOffset)]
        newLeftTop = leftTop
        newRightTop = rightTop
        if upDownSlice is not None:
            if max(newLeftBottom[1], newRightBottom[1]) > boardColor.shape[0] - upDownSlice + 5 * cp.mm2pix:
                extendLineIsOk = False
            else:
                extendLineIsOk = True
        else:
            if max(newLeftBottom[1], newRightBottom[1]) > boardColor.shape[0] - 10 * cp.mm2pix:
                extendLineIsOk = False
            else:
                extendLineIsOk = True

    newBox = [newLeftTop, newRightTop, newRightBottom, newLeftBottom] if extendLineIsOk else None
    useImage = boardColor.copy()
    cv.drawContours(useImage, [np.array([newLeftTop, newRightTop, newRightBottom, newLeftBottom])], -1, colorGreen, 3)
    image_show("GetNewBoxByExtendMiddle", useImage)
    return newBox


def get_new_box_by_middle_and_height(box, height, boardPos, boardColor):
    """
    通过中缝推测第一边或者第三边
    :param box:
    :param height:
    :param boardPos:
    :param boardColor:
    :return:
    """
    leftTop, rightTop, rightBottom, leftBottom = box
    leftK, leftB = get_line_k_and_b(leftTop, leftBottom)
    rightK, rightB = get_line_k_and_b(rightTop, rightBottom)
    heightOffset = height
    h, w = boardColor.shape[:2]

    if boardPos == "up":
        if leftK is None or rightK is None:
            newLeftTop = [int(leftTop[0]), int(leftBottom[1] - heightOffset)]
            newRightTop = [int(rightTop[0]), int(rightBottom[1] - heightOffset)]
        else:
            yOffset = abs(heightOffset * np.sin(trans_k_to_rad(leftK)))
            xOffset = abs(heightOffset * np.cos(trans_k_to_rad(leftK)))
            xOffset = xOffset if leftTop[0] - leftBottom[0] > 0 else -xOffset
            newLeftTop = [int(leftBottom[0] + xOffset), int(leftBottom[1] - yOffset)]
            newRightTop = [int(rightBottom[0] + xOffset), int(rightBottom[1] - yOffset)]

        newLeftBottom = leftBottom
        newRightBottom = rightBottom
        thirdLineToBoardEdge = get_two_line_distance([newLeftTop, newRightTop], [(0, 0), (w, 0)]) / cp.mm2pix
        pointIsOk = True if min(newLeftTop[1], newRightTop[1]) > 0 else False
    else:
        if leftK is None or rightK is None:
            newLeftBottom = [int(leftBottom[0]), int(leftTop[1] + heightOffset)]
            newRightBottom = [int(rightBottom[0]), int(rightTop[1] + heightOffset)]
        else:
            yOffset = abs(heightOffset * np.sin(trans_k_to_rad(leftK)))
            xOffset = abs(heightOffset * np.cos(trans_k_to_rad(leftK)))
            xOffset = xOffset if leftBottom[0] - leftTop[0] > 0 else -xOffset
            newLeftBottom = [int(leftTop[0] + xOffset), int(leftTop[1] + yOffset)]
            newRightBottom = [int(rightTop[0] + xOffset), int(rightTop[1] + yOffset)]
        newLeftTop = leftTop
        newRightTop = rightTop
        thirdLineToBoardEdge = get_two_line_distance([newLeftBottom, newRightBottom], [(0, h), (w, h)]) / cp.mm2pix
        pointIsOk = True if max(newLeftBottom[1], newRightBottom[1]) < boardColor.shape[0] else False

    isLonCropper = True if thirdLineToBoardEdge >= 70 else False
    useImage = boardColor.copy()
    cv.drawContours(useImage, [np.array([newLeftTop, newRightTop, newRightBottom, newLeftBottom])], -1, colorGreen, 3)
    image_show("ExtendEdgeByMiddle", useImage)
    print("推测的的内板位置到物料边缘的距离 = {:.2f},是否是长铜箔: {},推测区域是否合法: {}".format(thirdLineToBoardEdge, isLonCropper, pointIsOk))
    newBox = [newLeftTop, newRightTop, newRightBottom, newLeftBottom] if not isLonCropper and pointIsOk else None
    return newBox


def check_other_line_is_ok(board, box, lineOffset, checkPos, extend, boardPos):
    """
    检测另外一条线是否OK,如果OK,可以推测第三边
    :param board: floodFill的图片
    :param box: 找到的尺寸不符合的内板位置信息
    :param lineOffset: 首板记录的位置信息,内板到四边的位置信息
    :param checkPos: 是13边还是24边. 13边是高度方向,24边是宽度方向
    :param extend: 要延长的距离
    :param boardPos: 双拼板的位置,是上框还是下框
    :return:
    """
    extendErrorThres = 7  # 推测之后的位置阈值是7个mm
    longCropperLimitMm = 65  # 长铜箔不推测,防止铜箔折叠造成影响
    h, w = board.shape[:2]
    # 先求内框的四个顶点,还有物料区域外框的四个顶点
    p1, p2, p3, p4 = box
    p5, p6, p7, p8 = (0, 0), (w, 0), (w, h), (0, h)
    innerLine1, innerLine2, innerLine3, innerLine4 = (p1, p2), (p2, p3), (p3, p4), (p4, p1)
    outterLine1, outterLine2, outterLine3, outterLine4 = (p5, p6), (p6, p7), (p7, p8), (p8, p5)
    offsetTop = get_two_line_distance(innerLine1, outterLine1)
    offsetRight = get_two_line_distance(innerLine2, outterLine2)
    offsetBottom = get_two_line_distance(innerLine3, outterLine3)
    offsetLeft = get_two_line_distance(innerLine4, outterLine4)

    innerEdgePix = innerToEdgeThres * cp.mm2pix
    offsetCheckVal = np.subtract(np.array(lineOffset), np.array([offsetTop, offsetRight, offsetBottom, offsetLeft])).tolist()
    offsetCheckVal = [abs(x) for x in offsetCheckVal]
    isOkList = [True if offsetCheck <= innerEdgePix else False for offsetCheck in offsetCheckVal]
    leftTop, rightTop, rightBottom, leftBottom = box

    if checkPos == "24":
        # 矩形框从最上面开始,顺时针四边定位为1,2,3,4边
        if isOkList[1]:
            # 过滤掉是长铜箔的情况
            if lineOffset[3] > longCropperLimitMm * cp.mm2pix:
                return False, box
            # 如果第二边的位置是对的,就推测第四边.
            topK, topB = get_line_k_and_b(leftTop, rightTop)
            bottomK, bottomB = get_line_k_and_b(leftBottom, rightBottom)
            if topK == 0 or bottomK == 0:
                # 如果两条线都是水平的,也就是平行x轴,则y坐标不变,只要延长x坐标就行,因为是第二边推测第四遍,x的值应该是减小
                newLeftTop = [int(rightTop[0] - extend), rightTop[1]]
                newLeftBottom = [int(rightBottom[0] - extend), rightBottom[1]]
                newRightTop = rightTop
                newRightBottom = rightBottom
            else:
                # 先将延长的距离换算成x坐标的跨度,注意这里是有夹角的,本身的这个长度
                xOffset = abs(extend * np.cos(trans_k_to_rad(topK)))
                leftTopX = int(rightTop[0] - xOffset)
                leftTopY = topK * leftTopX + topB
                leftBottomX = int(rightBottom[0] - xOffset)
                leftBottomY = bottomK * leftBottomX + bottomB
                newLeftTop = [leftTopX, leftTopY]
                newLeftBottom = [leftBottomX, leftBottomY]
                newRightTop = rightTop
                newRightBottom = rightBottom

            # 判断推测出来的边和之前的首板保存的误差是否在范围内,这里推测的是第四边
            offsetLeftNew = get_two_line_distance((newLeftTop, newLeftBottom), outterLine4)
            extendOffsetError = (offsetLeftNew - lineOffset[3]) / cp.mm2pix
            print("通过第二边推测的第四遍和首板记录的误差值: {:.2f}".format(extendOffsetError))
            if abs(extendOffsetError) < extendErrorThres:
                return True, [newLeftTop, newRightTop, newRightBottom, newLeftBottom]
            else:
                return False, box
        elif isOkList[3]:
            # 如果第四边是对的,推测第二边.
            if lineOffset[1] > longCropperLimitMm * cp.mm2pix:
                return False, box
            topK, topB = get_line_k_and_b(leftTop, rightTop)
            bottomK, bottomB = get_line_k_and_b(leftBottom, rightBottom)
            if topK == 0 or bottomK == 0:
                newRightTop = [int(leftTop[0] + extend), leftTop[1]]
                newRightBottom = [int(leftBottom[0] + extend), leftBottom[1]]
                newLeftTop = leftTop
                newLeftBottom = leftBottom
            else:
                xOffset = abs(extend * np.cos(trans_k_to_rad(topK)))
                rightTopX = int(leftTop[0] + xOffset)
                rightTopY = int(topK * rightTopX + topB)
                rightBottomX = int(leftBottom[0] + xOffset)
                rightBottomY = int(bottomK * rightBottomX + bottomB)
                newRightTop = [rightTopX, rightTopY]
                newRightBottom = [rightBottomX, rightBottomY]
                newLeftTop = leftTop
                newLeftBottom = leftBottom

            offsetRightNew = get_two_line_distance((newRightBottom, newRightTop), outterLine2)
            extendOffsetError = (offsetRightNew - lineOffset[1]) / cp.mm2pix
            print("通过第三边推测的第二边和首板记录的误差值: {:.2f}".format(extendOffsetError))
            if abs(extendOffsetError) < extendErrorThres:
                return True, [newLeftTop, newRightTop, newRightBottom, newLeftBottom]
            else:
                return False, box
        else:
            return False, box
    else:
        # 推测高度方向的尺寸
        if isOkList[0]:  # 推测第三边
            if lineOffset[2] > longCropperLimitMm * cp.mm2pix and boardPos == "down":
                return False, box
            leftK, leftB = get_line_k_and_b(leftTop, leftBottom)
            rightK, rightB = get_line_k_and_b(rightTop, rightBottom)
            if leftK is None or rightK is None:
                # 如果是垂直的线,则x不变,y直接延长即可
                newLeftBottom = [leftTop[0], leftTop[1] + extend]
                newRightBottom = [rightTop[0], rightTop[1] + extend]
                newLeftTop = leftTop
                newRightTop = rightTop
            else:
                yOffset = abs(extend * np.sin(trans_k_to_rad(leftK)))
                leftBottomY = int(leftTop[1] + yOffset)
                leftBottomX = int((leftBottomY - leftB) / leftK)
                rightBottomY = int(rightTop[1] + yOffset)
                rightBottomX = int((rightBottomY - rightB) / rightK)
                newLeftBottom = [leftBottomX, leftBottomY]
                newRightBottom = [rightBottomX, rightBottomY]
                newLeftTop = leftTop
                newRightTop = rightTop

            offsetBottomNew = get_two_line_distance((newLeftBottom, newRightBottom), outterLine3)
            extendOffsetError = (offsetBottomNew - lineOffset[2]) / cp.mm2pix
            print("根据第一边推测的第三边和首板记录的误差值: {:.2f}".format(extendOffsetError))
            if abs(extendOffsetError) < extendErrorThres:
                return True, [newLeftTop, newRightTop, newRightBottom, newLeftBottom]
            else:
                return False, box
        elif isOkList[2]:
            # 推测第一边,第三边是对的,来推测第一边
            if lineOffset[0] > longCropperLimitMm * cp.mm2pix and boardPos == "up":
                return False, box
            leftK, leftB = get_line_k_and_b(leftTop, leftBottom)
            rightK, rightB = get_line_k_and_b(rightTop, rightBottom)
            if leftK is None or rightK is None:
                newLeftTop = [leftBottom[0], leftBottom[1] - extend]
                newRightTop = [rightBottom[0], rightBottom[1] - extend]
                newLeftBottom = leftBottom
                newRightBottom = rightBottom
            else:
                yOffset = abs(extend * np.sin(trans_k_to_rad(leftK)))
                leftTopY = int(leftBottom[1] - yOffset)
                leftTopX = int((leftTopY - leftB) / leftK)
                rightTopY = int(rightBottom[1] - yOffset)
                rightTopX = int((rightTopY - rightB) / rightK)
                newLeftTop = [leftTopX, leftTopY]
                newRightTop = [rightTopX, rightTopY]
                newLeftBottom = leftBottom
                newRightBottom = rightBottom
            offsetTopNew = get_two_line_distance((newLeftTop, newRightTop), outterLine1)
            extendOffsetError = (offsetTopNew - lineOffset[0]) / cp.mm2pix
            print("根据第三边推测的第一边和首板记录的误差值: {:.2f}".format(extendOffsetError))
            if abs(extendOffsetError) < extendErrorThres:
                return True, [newLeftTop, newRightTop, newRightBottom, newLeftBottom]
            else:
                return False, box
        else:
            return False, box


def trans_k_to_rad(k):
    """
    将斜率转换为弧度
    :param k:
    :return:
    """
    return np.arctan(k)


def get_angle_by_k(k):
    """
    根据k值去获取角度,单位是°
    :param k:
    :return:
    """
    if k == 0:
        return 0
    if k is None:
        return 90
    return np.arctan(k) * 180 / np.pi


def get_center_by_contour(cnt):
    """
    根据传入的轮廓获取到轮廓的中心,返回轮廓中心的int类型,轮廓中心算出来可能是小数,这里转换为int.
    :param cnt:传入的轮廓
    :return:因为是轮廓的中心,默认返回int类型
    """
    (centerX, centerY), *_ = cv.minAreaRect(cnt)
    return int(centerX + 0.5), int(centerY + 0.5)  # 四舍五入法将浮点数转换为int类型


def get_rect_points_clockwise(rectPoints):
    """
    根据矩形的四个顶点,获取它顺时针方向的四个顶点
    :param rectPoints: 矩形的四个顶点,顺序是不确定的
    :return:
    """
    rectPointsSorted = sorted(rectPoints, key=np.sum)
    leftTop = rectPointsSorted[0]
    rightBottom = rectPointsSorted[-1]
    secondPointX = int(rectPointsSorted[1][0])
    thirdPointX = int(rectPointsSorted[2][0])
    if secondPointX > thirdPointX:
        rightTop = rectPointsSorted[1]
        leftBottom = rectPointsSorted[2]
    else:
        rightTop = rectPointsSorted[2]
        leftBottom = rectPointsSorted[1]
    return leftTop, rightTop, rightBottom, leftBottom


def get_two_point_distance(p1, p2):
    """
    获取两点之间的距离,保留小数点后两位
    :param p1: 点1
    :param p2: 点2
    :return:
    """
    assert len(p1) == 2
    assert len(p2) == 2
    ds = np.sqrt((p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2)
    return round(ds, 2)


def get_board_without_angle_3(image, boxMaster, extend=0):
    """
    获取无角度的物料区域,是否延长,表示是否延长物料区域的四周,保证可以完整的看到物料区域.extend表示延长多少的意思.
    :param image:
    :param boxMaster:
    :param extend:延长多少的标识,0表示不延长
    :return:
    """
    (centerX, centerY), (w, h), angle = boxMaster
    widthHalf = int(w / 2)
    heightHalf = int(h / 2)
    yStart = max(int(centerY - heightHalf), 0)  # 之所以有零的情况,是因为旋转之后物料框的y起点可能跑到外面去了.
    yEnd = min(int(centerY + heightHalf), image.shape[0])  # yEnd也是一样的,有可能出去了
    xStart = max(int(centerX - widthHalf), 0)
    xEnd = min(int(centerX + widthHalf), image.shape[1])
    M = cv.getRotationMatrix2D((centerX, centerY), angle, 1)
    if extend != 0:
        if yStart - extend > 0:
            yStart = yStart - extend
        if yEnd + extend < image.shape[0]:
            yEnd = yEnd + extend

        if xStart - extend > 0:
            xStart = xStart - extend
        if xStart + extend < image.shape[1]:
            xEnd = xEnd + extend

    totalBoard = cv.warpAffine(image, M, (image.shape[1], image.shape[0]))[yStart:yEnd, xStart:xEnd]
    return totalBoard, [xStart, yStart]


def find_total_board(image, layoutMode):
    """
    查找物料区域,过滤掉小的面积.这个是竖着进板的物料区域查找
    :param image:
    :return:
    """
    if layoutMode == "竖排":
        thresUsed = image[:300, :]
    else:
        thresUsed = image[:, :300]
    image_show("totalBoardThresUsed", thresUsed)
    thresUsedVal = int(np.mean(thresUsed) + 15)
    print("物料区域查找的阈值: {}".format(thresUsedVal))
    filterArea = 4500
    T, board = cv.threshold(image, thresUsedVal, 255, cv.THRESH_BINARY)
    image_show("BoardThresOriginal", board)

    # 使用开操作去除掉小点
    kernel = cv.getStructuringElement(cv.MORPH_RECT, (3, 3))
    board = cv.morphologyEx(board, cv.MORPH_OPEN, kernel, iterations=5)
    image_show("BoardOpenned", board)

    cnts = cv.findContours(board.copy(), cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
    cnts = imutils.grab_contours(cnts)

    # 找到比较大的轮廓
    largeContours = [cnt for cnt in cnts if cv.contourArea(cnt) > filterArea]
    # 如果比较大的轮廓大于等于2,就连线
    if len(largeContours) >= 2:
        centerPoints = [get_center_by_contour(contour) for contour in largeContours]
        for index, center in enumerate(centerPoints):
            anotherCenter = centerPoints[(index + 1) % len(centerPoints)]
            cv.line(board, center, anotherCenter, 255, 20)
        image_show("JointCenterLine", board)

        # 连线之后再找一遍轮廓
        cnts = cv.findContours(board.copy(), cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
        cnts = imutils.grab_contours(cnts)
        cntFirstFinded = sorted(cnts, key=cv.contourArea, reverse=True)[0]
    elif len(largeContours) == 1:
        cntFirstFinded = largeContours[0]
    else:
        return False, None, None, None, None, None

    # 轮廓找到以后,将四个角的影响去除掉
    rectPoints = np.int0(cv.boxPoints(cv.minAreaRect(cntFirstFinded)))
    leftTop, rightTop, rightBottom, leftBottom = get_rect_points_clockwise(rectPoints)

    boardFinal = board.copy()
    moveWidth = int(get_two_point_distance(leftTop, rightTop) * 0.1)
    moveHeight = int(get_two_point_distance(leftTop, leftBottom) * 0.2)
    extend = 30
    boardFinal[leftTop[1] - extend:leftTop[1] + moveHeight, leftTop[0] - extend:leftTop[0] + moveWidth] = 0
    boardFinal[rightTop[1] - extend:rightTop[1] + moveHeight, rightTop[0] - moveWidth:rightTop[0] + extend] = 0
    boardFinal[leftBottom[1] - moveHeight:leftBottom[1] + extend, leftBottom[0] - extend:leftBottom[0] + moveWidth] = 0
    boardFinal[rightBottom[1] - moveHeight:rightBottom[1] + extend, rightBottom[0] - moveWidth:rightBottom[0] + extend] = 0
    image_show("FinalBoardAngleMoved", boardFinal)

    cnts = cv.findContours(boardFinal.copy(), cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
    cnts = imutils.grab_contours(cnts)
    largeContours = [cnt for cnt in cnts if cv.contourArea(cnt) > filterArea]

    if len(largeContours) >= 2:
        centerPoints = [get_center_by_contour(contour) for contour in largeContours]
        for index, center in enumerate(centerPoints):
            anotherCenter = centerPoints[(index + 1) % len(centerPoints)]
            cv.line(boardFinal, center, anotherCenter, 255, 20)
        image_show("JointCenterLineFinal", boardFinal)

        cnts = cv.findContours(boardFinal.copy(), cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
        cnts = imutils.grab_contours(cnts)
        cntFinalFinded = sorted(cnts, key=cv.contourArea, reverse=True)[0]
    elif len(largeContours) == 1:
        cntFinalFinded = largeContours[0]
    else:
        return False, None, None, None, None, None

    totalBoardThres = boardFinal.copy()
    cv.drawContours(totalBoardThres, cntFinalFinded, -1, 255, -1)
    image_show("TotalBoardThres", totalBoardThres)

    # 然后就是检测box角度问题,宽和高跟角度有关系
    box = cv.minAreaRect(cntFinalFinded)
    (cx, cy), (w, h), angle = box
    if angle > 70:
        angleNew = 90 - angle
        box = (cx, cy), (h, w), angleNew

    if angle < -70:
        angleNew = 90 + angle
        box = (cx, cy), (h, w), angleNew

    imageColor = cv.cvtColor(image, cv.COLOR_GRAY2BGR)
    rectPoints = np.int0(cv.boxPoints(box))
    cv.drawContours(imageColor, [rectPoints], -1, (0, 255, 0), 4)
    totalBoardNoAngle, totalBoardOffset = get_board_without_angle_3(image, box)
    return True, box, totalBoardThres, imageColor, totalBoardNoAngle, totalBoardOffset


def get_point_to_line_distance(p, lineK, lineB):
    """
    获取点到直线的距离
    :param p: 要获取到直线的距离的点
    :param lineK: 直线的斜率K
    :param lineB: 直线的斜距B
    :return:
    """
    if lineK == 0:
        # 如果直线是水平的,那么它的垂线是垂直x轴的,也就是没有斜率.
        verLineK = None
        verLineB = p[0]
    elif lineK == None:
        verLineK = 0
        verLineB = p[1]
    else:
        # 垂线和原来的直线的斜率的乘积是-1
        verLineK = -1 / lineK
        verLineB = p[1] - verLineK * p[0]

    # 然后根据垂线的斜率和斜距以及原来的线的斜率和斜距来获取交点
    intersection = get_two_lines_intersection(lineK, lineB, verLineK, verLineB)
    # 然后是获取两个点的距离,距离有正负,如果检测的直线,在传感器点的下方为正,在上方为负
    dis = get_two_point_distance(p, intersection)
    dis = dis if p[1] > intersection[1] else -dis
    return round(dis, 2)


def get_two_lines_intersection(k1, b1, k2, b2):
    """
    获取两条直线的交点
    :param k1: 第一条直线的斜率
    :param b1: 第一条直线的斜距
    :param k2: 第二条直线的斜率
    :param b2: 第二条直线的斜距
    :return: 返回两条直线的交点
    """
    if k1 == k2:
        return None, None
    elif k1 is None and k2 is not None:
        x = b1
        y = k2 * x + b2
        return x, y
    elif k2 is None and k1 is not None:
        x = b2
        y = k1 * x + b1
        return x, y
    else:
        x = (b1 - b2) / (k1 - k2) * (-1)
        y = k1 * x + b1
        return x, y


def get_distance_to_sensor(p1, p2):
    """
    获取物料的拍摄的第一边到传感器的距离
    :param p1: 物料拍摄的第一边的一个点
    :param p2: 物料拍摄的第一边的一个点
    :return:
    """
    sensorPoint = ((cp.tablePosStart + cp.tablePosEnd) // 2, cp.sensorPos)  # 传感器的那个点
    triggerLineK, triggerLineB = get_line_k_and_b(p1, p2)
    sensorTriggerDis = get_point_to_line_distance(sensorPoint, triggerLineK, triggerLineB)
    return sensorTriggerDis


def get_line_k_and_b(p1, p2):
    """
    根据两个点获取这条线的斜率和斜距
    :param p1: 线段的点1
    :param p2: 线段的点2
    :return:
    """
    if p1[0] == p2[0]:
        # 垂直于x轴,没有k值,斜率是None
        return None, p1[0]
    else:
        # k = (y2 - y1)/(x2-x1)
        # b = y - x * x
        k = (p2[1] - p1[1]) / (p2[0] - p1[0])
        b = p1[1] - k * p1[0]
        return k, b


def get_two_points_middle(p1, p2, res_is_int=False):
    """
    获取两个点的中点坐标
    :param p1: 点1的坐标
    :param p2: 点2的坐标
    :return:
    """
    assert len(p1) == 2
    assert len(p2) == 2
    middleX = (p1[0] + p2[0]) / 2
    middleY = (p1[1] + p2[1]) / 2
    if res_is_int:
        return int(middleX + 0.5), int(middleY + 0.5)
    return round(middleX, 2), round(middleY, 2)


def put_talbe_sensor_pos_to_image(totalBoard, tableVal, sensorVal):
    """
    将台布和传感器的信息写入到图像上去,主要是首板的时候做个检测,那个终检的线,大概就是传感器的位置
    :param totalBoard:物料区域的图,整个物料区域的图
    :param tableVal:台布的亮度
    :param sensorVal:传感器的亮度
    :return:
    """
    tablePoints = [(cp.tablePosStart, cp.sensorPos - 100),
                   (cp.tablePosEnd, cp.sensorPos - 100),
                   (cp.tablePosEnd, cp.sensorPos),
                   (cp.tablePosStart, cp.sensorPos)]
    sensorPoints = [(cp.tablePosStart, cp.sensorPos),
                    (cp.tablePosEnd, cp.sensorPos),
                    (cp.tablePosEnd, cp.sensorPos + 100),
                    (cp.tablePosStart, cp.sensorPos + 100)]
    tableCenter = get_two_points_middle(tablePoints[0], tablePoints[1])
    sensorCenter = get_two_points_middle(sensorPoints[2], sensorPoints[3])

    # 传感器检测的上下区域.台布区域以及物料区域
    cv.drawContours(totalBoard, [np.array(tablePoints)], -1, colorBlue, 3)
    cv.drawContours(totalBoard, [np.array(sensorPoints)], -1, colorGreen, 3)
    cv.line(totalBoard, (cp.tablePosStart, cp.sensorPos), (cp.tablePosEnd, cp.sensorPos),
            colorRed, 3)
    cv.putText(totalBoard, str(tableVal), (int(tableCenter[0]), int(tableCenter[1] - 20)),
               cv.FONT_HERSHEY_COMPLEX, 2, colorBlue, 3)
    cv.putText(totalBoard, str(sensorVal), (int(sensorCenter[0]), int(sensorCenter[1] + 50)),
               cv.FONT_HERSHEY_COMPLEX, 2, colorGreen, 3)
    image_show("TotalBoardTableVal", totalBoard)
    return totalBoard


def get_line_offset(upBox, downBox, totalBoard, totalBoardBox):
    """
    获取识别的内框的offset,单位是像素
    :param upBox: 上框位置
    :param downBox: 下框位置
    :param totalBoard: 整个物料区域
    :param totalBoardBox: 整个物料区域的位置信息
    :return:
    """
    if len(totalBoard.shape) >= 3:
        totalBoard = cv.cvtColor(totalBoard, cv.COLOR_BGR2GRAY)
    w, h = totalBoardBox[1]
    heightHalf = int(h / 2)
    upDownSlice = int(50 * cp.mm2pix)
    upBoard = totalBoard.copy()[:int(heightHalf + upDownSlice), :]
    upH, upW = upBoard.shape[:2]
    p1 = (0, 0)
    p2 = (upW, 0)
    p3 = (upW, upH)
    p4 = (0, upH)
    p5, p6, p7, p8 = upBox
    line1, line2, line3, line4 = [(p1, p2), (p2, p3), (p3, p4), (p4, p1)]
    line5, line6, line7, line8 = [(p5, p6), (p6, p7), (p7, p8), (p8, p5)]
    offset1 = get_two_line_distance(line1, line5)
    offset2 = get_two_line_distance(line2, line6)
    offset3 = get_two_line_distance(line3, line7)
    offset4 = get_two_line_distance(line4, line8)
    upOffset = [int(offset1), int(offset2), int(offset3), int(offset4)]

    # 下框的额坐标也要是totalBoard上的坐标
    p1 = (0, heightHalf - upDownSlice)
    p2 = (w, heightHalf - upDownSlice)
    p3 = (w, h)
    p4 = (0, h)
    p5, p6, p7, p8 = downBox
    line1, line2, line3, line4 = [(p1, p2), (p2, p3), (p3, p4), (p4, p1)]
    line5, line6, line7, line8 = [(p5, p6), (p6, p7), (p7, p8), (p8, p5)]
    offset1 = get_two_line_distance(line1, line5)
    offset2 = get_two_line_distance(line2, line6)
    offset3 = get_two_line_distance(line3, line7)
    offset4 = get_two_line_distance(line4, line8)
    downOffset = [int(offset1), int(offset2), int(offset3), int(offset4)]
    return upOffset, downOffset


def check_total_board_is_ok(image, totalHeightMax, totalHeightMin, boardBreak,
                            jointNumber, layoutMode="竖排"):
    """
    检测物料区域是否OK,主要作用是考虑来料是否正常,如果不正常,就要提示工人进行操作,还要考虑是否是dummy板
    :param image:
    :param totalHeightMax:
    :param totalHeightMin:
    :param boardBreak:
    :param jointNumber:
    :return:
    """
    roi_h = 120
    h, w = image.shape[:2]
    if layoutMode == "竖排":
        table_ystart, table_yend = cp.sensorPos - roi_h, cp.sensorPos
        table_xstart, table_xend = cp.tablePosStart, cp.tablePosEnd
        sensor_ystart, sensor_yend = cp.sensorPos, cp.sensorPos + roi_h
        sensor_xstart, sensor_xend = cp.tablePosStart, cp.tablePosEnd
    else:
        table_ystart, table_yend = h - cp.tablePosEnd, h - cp.tablePosStart
        table_xstart, table_xend = cp.sensorPos - roi_h, cp.sensorPos
        sensor_ystart, sensor_yend = h - cp.tablePosEnd, h - cp.tablePosStart
        sensor_xstart, sensor_xend = cp.sensorPos, cp.sensorPos + roi_h
    table_roi = image[table_ystart:table_yend, table_xstart:table_xend]
    sensor_roi = image[sensor_ystart:sensor_yend, sensor_xstart:sensor_xend]
    tablePosVal = int(np.mean(table_roi))
    sensorPosVal = int(np.mean(sensor_roi))
    (isOk, totalBoardBox, totalBoardThresWithAngle, totalBoardResWithAngle,
     totalBoardNoAngle, totalBoardOffset) = find_total_board(image, layoutMode)
    if not isOk:
        # 如果物料区域没有找到,就返回物料区域没有找到
        hintInfo = "物料区域轮廓没有找到,请检查相机下面的传感器是否有异物,光源是否打开,亮度是否太低!"
        return ("noBoard", hintInfo, totalBoardBox, totalBoardNoAngle, totalBoardOffset,
                totalBoardThresWithAngle, totalBoardResWithAngle)

    # 如果物料区域找到了
    rectPoints = np.int0(cv.boxPoints(totalBoardBox))
    leftTop, rightTop, rightBottom, leftBottom = get_rect_points_clockwise(rectPoints)
    if layoutMode == "竖排":
        # 计算物料边缘到传感器的距离,拍摄的点在传感器的下方为正,在上方为负
        distanceToSensorMm = get_distance_to_sensor(leftTop, rightTop) / cp.mm2pix
    else:
        sensor_point = (cp.sensorPos, (w - cp.tablePosStart + w - cp.tablePosEnd) // 2)
        trigger_line_k, trigger_line_b = get_line_k_and_b(leftTop, leftBottom)
        distanceToSensorMm = get_point_to_line_distance(sensor_point, trigger_line_k, trigger_line_b)

    # 先将阈值写到图像上去
    w, h = totalBoardBox[1]
    widthChecked, heightChecked = w / cp.mm2pix, h / cp.mm2pix
    if not boardBreak:
        maxThres = totalHeightMax
        minThres = totalHeightMin
    else:
        maxThres = totalHeightMax + 200
        minThres = totalHeightMin
    if layoutMode == "竖排":
        totalBoardResWithAngle = put_talbe_sensor_pos_to_image(
            totalBoardResWithAngle, tablePosVal, sensorPosVal)
    else:
        image_color = totalBoardResWithAngle.copy()
        table_points = [(table_xstart, table_ystart), (table_xend, table_ystart),
                        (table_xend, table_yend), (table_xstart, table_yend)]
        sensor_points = [(sensor_xstart, sensor_ystart), (sensor_xend, sensor_ystart),
                         (sensor_xend, sensor_yend), (sensor_xstart, sensor_yend)]
        cv.drawContours(image_color, [np.array(table_points)], -1, colorBlue, 3)
        cv.drawContours(image_color, [np.array(sensor_points)], -1, colorGreen, 3)
        table_center = get_two_points_middle(table_points[0], table_points[3], res_is_int=True)
        sensor_center = get_two_points_middle(sensor_points[1], sensor_points[2], res_is_int=True)
        table_val_pos = table_center[0] - 50, table_center[1]
        sensor_val_pos = sensor_center[0] + 20, sensor_center[1]
        cv.drawContours(image_color, [np.array(table_points)], -1, colorBlue, 3)
        cv.drawContours(image_color, [np.array(sensor_points)], -1, colorGreen, 3)
        cv.putText(image_color, str(tablePosVal), table_val_pos, cv.FONT_HERSHEY_COMPLEX,
                   2, colorBlue, 3)
        cv.putText(image_color, str(sensorPosVal), sensor_val_pos, cv.FONT_HERSHEY_COMPLEX,
                   2, colorGreen, 3)
        totalBoardResWithAngle = image_color.copy()
    image_show("TotalBoardRes", totalBoardResWithAngle)
    # 如果传感器的位置有问题,就自动重拍
    if distanceToSensorMm > sensorDistanceThresBig or distanceToSensorMm < -sensorDistanceThresSmall:
        hintInfo = "物料检测,拍照起始位置检测距离传感器太远,检测距离: {:.2f},ok范围: [{},{}]".format(
            distanceToSensorMm, -sensorDistanceThresSmall, sensorDistanceThresBig)
        return "sensorPosError", hintInfo, totalBoardBox, totalBoardNoAngle, totalBoardOffset, totalBoardThresWithAngle, totalBoardResWithAngle

    hintInfo = "物料检测,物料亮度:{},ok范围:大于{},台布亮度:{},ok范围:小于{},物料宽:{:.2f},物料高:{:.2f}," \
               "高度ok范围:({},{}),传感器位置偏差:{:.2f},ok范围:({},{})".format(sensorPosVal, cp.sensorValUsed, tablePosVal, cp.tableValUsed,
                                                                                widthChecked, heightChecked, minThres, maxThres, distanceToSensorMm,
                                                                                -sensorDistanceThresSmall, sensorDistanceThresBig)
    if sensorPosVal < cp.sensorValUsed or tablePosVal > cp.tableValUsed:
        return "totalBoardError", hintInfo, totalBoardBox, totalBoardNoAngle, totalBoardOffset, totalBoardThresWithAngle, totalBoardResWithAngle

    # 检测是否是dummy板,这个在景旺项目上才有用,其他项目上可以忽略
    totalBoardVal = int(np.mean(totalBoardNoAngle))  # 物料区域的平均亮度
    dummyWidthMaxMm = dummyWidthMm + dummySizeThres
    dummyWidthMinMm = dummyWidthMm - dummySizeThres
    dummyHeightMaxMm = dummyHeightMm + dummySizeThres
    dummyHeightMinMm = dummyHeightMm - dummySizeThres

    # if (dummyWidthMinMm < widthChecked < dummyWidthMaxMm and dummyHeightMinMm < dummyHeightMm < dummyHeightMaxMm) or \
    #         (dummyWidthMinMm < heightChecked < dummyWidthMaxMm and dummyHeightMinMm < dummyHeightMm < dummyHeightMaxMm) \
    #         and totalBoardVal < dumyyBoardValThres:
    #     hintInfo = "物料检测,检测到dummy板:检测的物料宽高:({:.2f},{:.2f}),dummy板样板宽高:({:.2f},{:.2f}),物料平均亮度:{},dummy板样板阈值:{}".format(
    #         widthChecked, heightChecked, dummyWidthMm, dummyHeightMm, totalBoardVal, dumyyBoardValThres)
    #     return "dummyBoard", hintInfo, totalBoardBox, totalBoardNoAngle, totalBoardOffset, totalBoardThresWithAngle, totalBoardResWithAngle

    if jointNumber == "2":
        # 如果是两拼板,就检测下高度,单拼的和三拼的就不检测了.
        if heightChecked < minThres or heightChecked > maxThres:
            hintInfo = "物料检测,高度检测未通过,检测物料高度:{:.2f},高度ok范围:({},{})".format(heightChecked, minThres, maxThres)
            return "totalBoardError", hintInfo, totalBoardBox, totalBoardNoAngle, totalBoardOffset, totalBoardThresWithAngle, totalBoardResWithAngle

    return "ok", hintInfo, totalBoardBox, totalBoardNoAngle, totalBoardOffset, totalBoardThresWithAngle, totalBoardResWithAngle


def get_sum_abs(iter):
    """
    计算可迭代对象的序列绝对值的和
    :param iter: 可迭代对象
    :return:
    """
    return sum([abs(x) for x in iter])


def check_two_box_is_overlap(upBox, downBox):
    """
    判断两个矩形是否有重合区域,也就是折叠区域
    :param upBox: 上框的四个顶点
    :param downBox: 下框的四个顶点
    :return:
    """
    isOverlap = False
    for point in upBox:
        if check_point_in_rect(point, downBox):
            isOverlap = True

    for point in downBox:
        if check_point_in_rect(point, upBox):
            isOverlap = True
    return isOverlap


def check_point_in_rect(point, box):
    """
        判断的规则是:
        利用的夹角的正负值,可以确定是顺时针还是逆时针方向.如果在矩形的内部的话,肯定是全部大于90度或者全部小于90度.
        这样就可以利用向量的点积的正负号来判断夹角是大于90度还是小于90度.
        判断一个点是否在矩形内,box是识别出来的一个框的矩形,point是另外一个框的一个点.
        :param point:矩形框的四个顶点的某一个点
        :param box:另外一个矩形框的四个顶点
        :return:
        """
    x, y = point
    Ax, Ay = box[0]
    Bx, By = box[3]
    Cx, Cy = box[2]
    Dx, Dy = box[1]
    a = (Bx - Ax) * (y - Ay) - (By - Ay) * (x - Ax)
    b = (Cx - Bx) * (y - By) - (Cy - By) * (x - Bx)
    c = (Dx - Cx) * (y - Cy) - (Dy - Cy) * (x - Cx)
    d = (Ax - Dx) * (y - Dy) - (Ay - Dy) * (x - Dx)
    if ((a > 0 and b > 0 and c > 0 and d > 0) or (a < 0 and b < 0 and c < 0 and d < 0)):
        return True
    else:
        return False


def get_size_scale_image(image, sizeScale=2):
    """
    按照比例缩放后的图像,获取.比例>1,代表是缩小,小于1代表是放大,函数主要用来缩小图像
    :param image:
    :param sizeScale: 缩小比例
    :return:
    """
    h, w = image.shape[:2]
    newWidth, newHeight = int(w // sizeScale), int(h // sizeScale)
    newImage = cv.resize(image, (newWidth, newHeight), interpolation=cv.INTER_AREA)
    return newImage


def get_linear_fit_k_vertical(pointsMat):
    """
    垂直方向获取拟合直线,垂直方向上,有角度可能接近90度,如果大于一定的值,就认为是None
    :param pointsMat: 要拟合的直线的四个点
    :return:
    """
    outPut = cv.fitLine(pointsMat, cv.DIST_L2, 0, 0.01, 0.01)
    if outPut[0] != 0:
        k = outPut[1] / outPut[0]
        k = k[0]
        if abs(k) > 1146:
            k = None
    else:
        k = None
    return k


def find_third_edge_line(totalBoard, totalBoardThres, extend):
    """
    物料区域的第三边的查找逻辑
    :param totalBoard:
    :param totalBoardThres:
    :param extend:
    :return:
    """
    try:
        totalBoardColor = cv.cvtColor(totalBoard, cv.COLOR_GRAY2BGR)
        image_show("TotalBoardThres", totalBoardThres)
        sliceHeight = int(0.8 * totalBoard.shape[0])
        board = totalBoardThres.copy()[sliceHeight:, :]
        scharrX = cv.Scharr(board, cv.CV_64F, 1, 0)
        scharrY = cv.Scharr(board, cv.CV_64F, 0, 1)
        scharrX = cv.convertScaleAbs(scharrX)
        scharrY = cv.convertScaleAbs(scharrY)
        scharrXY = cv.addWeighted(scharrX, 0.3, scharrY, 0.5, 0)
        board = scharrXY.copy()
        image_show("ScharrXYThirdEdge", board)
        minLineLengthEdge = int(20 * cp.mm2pix)
        maxLineGap = int(10 * cp.mm2pix)
        yMin = board.shape[0] - int(50 * cp.mm2pix)
        lines = cv.HoughLinesP(board, 1, np.pi / 180, 150, minLineLengthEdge, maxLineGap)
        boardShow = totalBoardColor.copy()
        linesList = []
        colorIndex = 0
        for line in lines:
            x1, y1, x2, y2 = line[0]
            lineLength = get_two_point_distance((x1, y1), (x2, y2))
            if abs(y2 - y1) < 20 * cp.mm2pix and abs(x2 - x1) > 10 * cp.mm2pix and y1 > yMin and y2 > yMin and lineLength > minLineLengthEdge:
                y1 += sliceHeight
                y2 += sliceHeight
                linesList.append([x1, y1, (y1 + y2) / 2, lineLength])
                linesList.append([x2, y2, (y1 + y2) / 2, lineLength])
                cv.line(boardShow, (x1, y1), (x2, y2), colorList[colorIndex % 3], 3)
                colorIndex += 1

        linesListSortedY = sorted(linesList, key=lambda x: x[2], reverse=True)
        linesSlice = len(linesListSortedY) // 2
        if linesSlice % 2 != 0:
            linesSlice -= 1

        linesList = linesListSortedY[:linesSlice]
        # 再按照长度进行排序
        linesListSortedLength = sorted(linesList, key=lambda x: x[3], reverse=True)
        print("LinesSortedLength: 个数: {}, {}".format(len(linesListSortedLength), linesListSortedLength))
        linesList = linesListSortedLength.copy()
        if len(linesList) >= 2:
            linesFinal = linesListSortedLength[:2]
            print("linesFinalFinded 长度: {:.2f}, {}".format(linesFinal[0][3] / cp.mm2pix, linesFinal))
            linesListFinal = [line[:2] for line in linesFinal]
            boardShow = totalBoardColor.copy()
            p1, p2 = linesListFinal
            cv.line(boardShow, tuple(p1), tuple(p2), colorBlue, 3)
            image_show("FinalLineFinded", boardShow)
            p1, p2 = (p1[0], p1[1] - extend), (p2[0], p2[1] - extend)
            bottomCutLineK, bottomCutLineB = get_line_k_and_b(p1, p2)
            return bottomCutLineK, bottomCutLineB
        else:
            return None, None
    except Exception as e:
        errorInfo = "in find_third_edge_line() error: {}".format(str(e))
        print(errorInfo)
        return None, None


def find_first_edge_line(totalBoard, totalBoardThres, extend, leftTop, rightTop):
    """
    找物料区域的第一边
    :param totalBoard:totalBoard延长了上下左右
    :param totalBoardThres: totalBoardThres延长了上下左右
    :param extend: 延长的尺寸
    :return:
    """
    try:
        totalBoardColor = cv.cvtColor(totalBoard, cv.COLOR_GRAY2BGR)
        image_show("TotalBoardThres", totalBoardThres)
        board = totalBoardThres.copy()[:int(totalBoardThres.shape[0] / 4), :]
        scharrX = cv.Scharr(board, cv.CV_64F, 1, 0)
        scharrY = cv.Scharr(board, cv.CV_64F, 0, 1)
        scharrX = cv.convertScaleAbs(scharrX)
        scharrY = cv.convertScaleAbs(scharrY)
        scharrXY = cv.addWeighted(scharrX, 0.3, scharrY, 0.5, 0)
        board = scharrXY.copy()
        image_show("ScharrXYEdge", board)
        minLineLenghtEdge = int(20 * cp.mm2pix)
        maxLineGap = 30
        yMax = 50 * cp.mm2pix
        lines = cv.HoughLinesP(board, 1, np.pi / 180, 150, minLineLenghtEdge, maxLineGap)
        boardShow = totalBoardColor.copy()
        linesList = []
        colorIndex = 0
        topLineK = get_line_k_and_b(leftTop, rightTop)[0]
        topAngle = get_angle_by_k(topLineK)
        if lines is not None:
            for line in lines:
                x1, y1, x2, y2 = line[0]
                lineK = get_line_k_and_b((x1, y1), (x2, y2))[0]
                lineAngle = get_angle_by_k(lineK)
                lineAngleError = lineAngle - topAngle
                lineLength = get_two_point_distance((x1, y1), (x2, y2))
                # 角度误差要小于0.6
                lineErrorThres = 0.6
                if abs(y2 - y1) < 20 * cp.mm2pix and abs(x2 - x1) > 10 * cp.mm2pix and y1 < yMax and y2 < yMax and lineLength > minLineLenghtEdge and \
                        abs(lineAngleError) < lineErrorThres:
                    lineTemp = [(x1, y1), (x2, y2)]
                    linesList.append([lineTemp, (y1 + y2) / 2, lineLength, lineAngleError])
                    cv.line(boardShow, (x1, y1), (x2, y2), colorList[colorIndex % 3], 3)
                    colorIndex += 1
        # 先按照y进行排序,然后截取前面的1/2进行判断.注意两个点代表一条线,截取掉的个数要是偶数
        linesSortedY = sorted(linesList, key=lambda x: x[1])
        linesSlice = int(len(linesSortedY) / 2)
        linesList = linesSortedY[:linesSlice]
        image_show("EdgeFinded", boardShow)
        # 然后再按照线段的长度进行排序
        linesSortedLength = sorted(linesList, key=lambda x: x[2], reverse=True)
        print("linesSortedLength: 个数:{}, {}".format(len(linesSortedLength), linesSortedLength))
        linesList = linesSortedLength.copy()
        if len(linesList) >= 1:
            linesFinal = linesSortedLength[0]
            print("linesFinalFinded 长度: {:.2f} mm,角度差: {:.2f}".format(linesFinal[2] / cp.mm2pix, linesFinal[3]))
            boardShow = totalBoardColor.copy()
            p1, p2 = linesFinal[0]
            cv.line(boardShow, tuple(p1), tuple(p2), colorBlue, 3)
            image_show("FinalLineFinded", boardShow)
            # 将延长的去除掉,图像别延长了,要放到延长之前的图上去,y坐标要去除掉之前增加的部分
            p1, p2 = (p1[0], p1[1] - extend), (p2[0], p2[1] - extend)
            topCutLineK, topCutLineB = get_line_k_and_b(p1, p2)
            return topCutLineK, topCutLineB
        else:
            return None, None
    except Exception as e:
        errorInfo = "in find_first_edge_line() error: {}".format(str(e))
        print(errorInfo)
        return None, None


def find_third_edge_line(totalBoard, totalBoardThres, extend):
    """
    物料区域的第三边的查找逻辑
    :param totalBoard:
    :param totalBoardThres:
    :param extend:
    :return:
    """
    try:
        totalBoardColor = cv.cvtColor(totalBoard, cv.COLOR_GRAY2BGR)
        image_show("TotalBoardThres", totalBoardThres)
        sliceHeight = int(0.8 * totalBoard.shape[0])
        board = totalBoardThres.copy()[sliceHeight:, :]
        scharrX = cv.Scharr(board, cv.CV_64F, 1, 0)
        scharrY = cv.Scharr(board, cv.CV_64F, 0, 1)
        scharrX = cv.convertScaleAbs(scharrX)
        scharrY = cv.convertScaleAbs(scharrY)
        scharrXY = cv.addWeighted(scharrX, 0.3, scharrY, 0.5, 0)
        board = scharrXY.copy()
        image_show("ScharrXYThirdEdge", board)
        minLineLengthEdge = int(20 * cp.mm2pix)
        maxLineGap = int(10 * cp.mm2pix)
        yMin = board.shape[0] - int(50 * cp.mm2pix)
        lines = cv.HoughLinesP(board, 1, np.pi / 180, 150, minLineLengthEdge, maxLineGap)
        boardShow = totalBoardColor.copy()
        linesList = []
        colorIndex = 0
        for line in lines:
            x1, y1, x2, y2 = line[0]
            lineLength = get_two_point_distance((x1, y1), (x2, y2))
            if abs(y2 - y1) < 20 * cp.mm2pix and abs(x2 - x1) > 10 * cp.mm2pix and y1 > yMin and y2 > yMin and lineLength > minLineLengthEdge:
                y1 += sliceHeight
                y2 += sliceHeight
                linesList.append([x1, y1, (y1 + y2) / 2, lineLength])
                linesList.append([x2, y2, (y1 + y2) / 2, lineLength])
                cv.line(boardShow, (x1, y1), (x2, y2), colorList[colorIndex % 3], 3)
                colorIndex += 1

        linesListSortedY = sorted(linesList, key=lambda x: x[2], reverse=True)
        linesSlice = len(linesListSortedY) // 2
        if linesSlice % 2 != 0:
            linesSlice -= 1

        linesList = linesListSortedY[:linesSlice]
        # 再按照长度进行排序
        linesListSortedLength = sorted(linesList, key=lambda x: x[3], reverse=True)
        print("LinesSortedLength: 个数: {}, {}".format(len(linesListSortedLength), linesListSortedLength))
        linesList = linesListSortedLength.copy()
        if len(linesList) >= 2:
            linesFinal = linesListSortedLength[:2]
            print("linesFinalFinded 长度: {:.2f}, {}".format(linesFinal[0][3] / cp.mm2pix, linesFinal))
            linesListFinal = [line[:2] for line in linesFinal]
            boardShow = totalBoardColor.copy()
            p1, p2 = linesListFinal
            cv.line(boardShow, tuple(p1), tuple(p2), colorBlue, 3)
            image_show("FinalLineFinded", boardShow)
            p1, p2 = (p1[0], p1[1] - extend), (p2[0], p2[1] - extend)
            bottomCutLineK, bottomCutLineB = get_line_k_and_b(p1, p2)
            return bottomCutLineK, bottomCutLineB
        else:
            return None, None
    except Exception as e:
        errorInfo = "in find_third_edge_line() error: {}".format(str(e))
        print(errorInfo)
        return None, None


def get_middle_cut_line(p1, p2, p3, p4):
    """
    根据p1和p2这条线以及p3和p4组成的线找角平分线
    :param p1: 线1的点1
    :param p2: 线1的点2
    :param p3: 线2的点1
    :param p4: 线2的点2
    :return:
    """
    upK, upB = get_line_k_and_b(p1, p2)
    downK, downB = get_line_k_and_b(p3, p4)
    intersection = get_two_lines_intersection(upK, upB, downK, downB)
    # 如果本身是平行线,没有交点的时候
    if intersection[0] is None:
        middleLineK = upK
        middleLineP = np.add(p1, p3) / 2
        middleLineB = middleLineP[1] - middleLineK * middleLineP[0]
    else:
        middleLineK = np.tan((np.arctan(upK) + np.arctan(downK)) / 2)  # 角平分线公式
        middleLineB = intersection[1] - middleLineK * intersection[0]
    return middleLineK, middleLineB


def pix_pos_to_robot(pixPos, matrix):
    """
    根据单个矩阵去获取像素坐标对应的裁切机坐标
    :param pixPos:
    :param matrix:
    :return:
    """
    x, y = pixPos
    robotPos = np.dot((x, y, 1), np.transpose(matrix))
    return round(robotPos[0], 2), round(robotPos[1], 2)


def get_board_length_2p(topLine, middleLine, bottomLine):
    """
    已知第一条切线,中缝,以及第三条切线,计算第一块板长和第二块板长
    :param topLine:
    :param middleLine:
    :param bottomLine:
    :return:
    """
    # 先建立坐标系,从裁切机的原点方向来看.将坐标x轴和y轴调换一下,然后求中缝的中垂线和topLine以及bottomLine的交点就是板长
    lineNew = []
    for line in [topLine, middleLine, bottomLine]:
        lineTemp = [[line[0][1], line[0][0]], [line[1][1], line[1][0]]]
        lineNew.append(lineTemp)

    topLine, middleLine, bottomLine = lineNew
    middleLineMidP = get_two_points_middle(middleLine[0], middleLine[1])

    topK, topB = get_line_k_and_b(topLine[0], topLine[1])
    midK, midB = get_line_k_and_b(middleLine[0], middleLine[1])
    bottomK, bottomB = get_line_k_and_b(bottomLine[0], bottomLine[1])

    if midK is None:
        midVerTopIntersection = [middleLineMidP[0], topK * middleLineMidP[0] + topB]
        midVerBottomIntersection = [middleLineMidP[0], bottomK * middleLineMidP[0] + bottomB]
    else:
        midVerK = -1 / midK
        midVerB = middleLineMidP[1] - midVerK * middleLineMidP[0]
        midVerTopIntersection = get_two_lines_intersection(midVerK, midVerB, topK, topB)
        midVerBottomIntersection = get_two_lines_intersection(midVerK, midVerB, bottomK, bottomB)

    topToMiddle = get_two_point_distance(middleLineMidP, midVerTopIntersection)
    topToBottom = get_two_point_distance(middleLineMidP, midVerBottomIntersection) + topToMiddle
    return round(topToMiddle, 2), round(topToBottom, 2)


class Line(object):
    def __init__(self, x1, y1, x2, y2):
        self.x1 = x1
        self.y1 = y1
        self.x2 = x2
        self.y2 = y2

    def vector(self):
        c = (self.x1 - self.x2, self.y1 - self.y2)
        return c

    def length(self):
        length = math.sqrt(self.vector()[0] * self.vector()[0] + self.vector()[1] * self.vector()[1])
        return length


# def get_two_line_angle(line1, line2):
#     """
#     获取第一条线到第二条线的角度,站在裁切机的原点来看,规定,line1到line2逆时针为正,一般第一条线是要调整的线,第二条线是检测的线.
#     :param line1: 实际上要检测的线
#     :param line2: 铡刀能够检测的线,流程上是这条线先调整到跟铡刀平行,然后line1,就是实际要检测的中缝,再旋转到跟这条线重合.
#     :return:
#     """
#     p1, p2 = line1
#     p3, p4 = line2
#     line1 = Line(p1[1], p1[0], p2[1], p2[0])
#     line2 = Line(p3[1], p3[0], p4[1], p4[0])
#     v1 = line1.vector()
#     v2 = line2.vector()
#     len1 = line1.length()
#     len2 = line2.length()
#     # 可以判断方向
#     direction = v1[0] * v2[1] - v1[1] * v2[0]
#
#     # 获取内积
#     inner_mul = np.dot(v1, v2)
#     # 获取模积
#     model_mul = len1 * len2
#     # 向量夹角公式
#     cosx = inner_mul / model_mul
#     # 防止计算误差,两条线平行的时候值超过1
#     cosx = 1 if cosx > 1.0 else cosx
#     top_middle_angle = np.arccos(cosx) * (180 / np.pi)
#     top_middle_angle = top_middle_angle if direction > 0 else -top_middle_angle
#     return round(top_middle_angle, 2)


def get_two_lines_angular_bisector(firstK, firstB, secondK, secondB, firstMiddleY, secondMiddleY):
    """
    根据两条线的斜率和斜距求出来它的角平分线的斜率和斜距
    :param k1:
    :param b1:
    :param k2:
    :param b2:
    :return:
    """

    if firstK == secondK:
        middleK = firstK
    else:
        middleK = np.tan((np.arctan(firstK) + np.arctan(secondK)) / 2)
    if firstK != 0:
        upPoint = ((firstMiddleY - firstB) / firstK, firstMiddleY)
        upB = upPoint[1] - middleK * upPoint[0]
    else:
        upB = firstMiddleY
    if secondK != 0:
        downPoint = ((secondMiddleY - secondB) / secondK, secondMiddleY)
        downB = downPoint[1] - middleK * downPoint[0]
    else:
        downB = secondMiddleY

    middleK = round(middleK, 5)

    return (middleK, upB), (middleK, downB)


def get_board_length_three(topLine, firstMiddleLine, seconMiddleLine, bottomLine):
    """
    根据四条线,获取它们之间的间距
    :param topLine:
    :param firstMiddleLine:
    :param seconMiddleLine:
    :param bottomLine:
    :return:
    """
    firstLength = get_two_line_distance(topLine, firstMiddleLine)
    secondLength = get_two_line_distance(firstMiddleLine, seconMiddleLine)
    thirdLength = get_two_line_distance(bottomLine, seconMiddleLine)
    return firstLength, secondLength, thirdLength


def get_width_height_by_image_name_1p(imageName):
    """
    单拼板根据名字获取尺寸信息
    :param imageName:
    :return:
    """
    if "w_" not in imageName or "h_" not in imageName:
        return None, None, None

    widthIndex = imageName.index("w_")
    heightIndex = imageName.index("h_")
    newW = imageName[widthIndex + 2:widthIndex + 6]
    newH = imageName[heightIndex + 2:heightIndex + 6]
    if "b_" not in imageName:
        boxOffset = [0, 0, 0, 0]
    else:
        dataList = imageName.split("--")
        for data in dataList:
            if "b_" in data:
                boxOffset = data.split("_")[1:5]
                break
        else:
            boxOffset = [0, 0, 0, 0]
    return int(newW), int(newH), [int(x) for x in boxOffset]
